.. _Указатели:

Указатели
=========

.. index::
    указатель
    pointer
    reference
    ссылка

Указатели - это переменные, которые обеспечивают доступ к другим переменным. Значением
указателя является адрес переменной, к которой он предоставляет доступ. Указатели могут указывать на любой тип переменных, объектов и даже на другие указатели. В
этой главе я буду называть все это просто переменными.
Указатели - это низкоуровневые функции микропроцессоров. Они являются важной частью
системного программирования.
Синтаксис и семантика указателей в D напрямую унаследованы от C.
Хотя указатели, как известно, являются самой сложной для понимания особенностью языка C,
в D они не должны быть такими сложными. Это связано с тем, что другие возможности D, которые
семантически близкие к указателям, более полезны в ситуациях, когда указатели
в других языках. Когда идеи, лежащие в основе указателей, уже
когда идеи, лежащие в основе указателей, уже понятны из других возможностей D, указатели должны быть проще для восприятия.
Короткие примеры, приведенные в большей части этой главы, очень просты.
Программы в конце главы будут более реалистичными.
Имена типа ptr (сокращение от "указатель"), которые я использовал в этих примерах
не следует рассматривать как полезные имена вообще. Как всегда, имена должны быть
выбирать так, чтобы они были более осмысленными и объясняющими в реальных программах.

Понятие ссылки
--------------

.. index::
    ref
    foreach

Хотя в предыдущих главах мы уже неоднократно сталкивались со ссылками,давайте подытожим это понятие еще раз.

**Переменные ref в циклах foreach**

Как мы уже видели в главе "Цикл foreach" (:ref:`цикл foreach<глава_29_start>`), обычно переменные цикла являются копиями элементов:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int[] numbers = [1, 11, 111];
        foreach (number; numbers)
        {
            number = 0; // ← the copy changes, not the element
        }
        writeln("After the loop: ", numbers);
    }

Число, которому каждый раз присваивается 0, является копией одного из элементов массива
массива. Изменение этой копии не приводит к изменению элемента:

.. code-block:: console

    After the loop: [1, 11, 111]

Когда необходимо изменить фактические элементы, переменная foreach должна быть
должна быть определена как ref:

.. code-block:: d

    foreach (ref number; numbers)
    {
        number = 0; // ← the actual element changes
    }

Это временное число является ссылкой на реальный элемент в массиве:

.. code-block:: console

    After the loop: [0, 0, 0]

**параметры функции ref**

Как мы уже видели в главе "Параметры функций" (:ref:`Параметры функций<глава_35_start>`), параметры
типов значений обычно являются копиями аргументов:

.. code-block:: d

    import std.stdio;

    void addHalf(double value)
    {
        value += 0.5; // ← Does not affect 'value' in main
    }

    void main()
    {
        double value = 1.5;
        addHalf(value);
        writeln("The value after calling the function: ", value);
    }

Поскольку параметр функции не определен как ref, присваивание внутри функции
влияет только на локальную переменную. Переменная в main() не затрагивается:

.. code-block:: console

    The value after calling the function: 1.5

Ключевое слово ref превращает параметр функции в ссылку на аргумент:

.. code-block:: d

    void addHalf(ref double value)
    {
        value += 0.5;
    }

На этот раз переменная в main() изменяется:

.. code-block:: console

    The value after calling the function: 2

**Ссылочные типы**

Некоторые типы являются ссылочными. Переменные таких типов предоставляют доступ к отдельным
переменным:
* Переменные класса
* Фрагменты
* Ассоциативные массивы
Мы уже видели это различие в главе "Типы значений и ссылочные типы
(:ref:`Типы значения и ссылочные типы<глава_34_start>`). Следующий пример демонстрирует ссылочные типы на примере двух классов
переменных:


.. code-block:: d

    import std.stdio;

    class Pen
    {
        double ink;
        this()
        {
            ink = 15;
        }

        void use(double amount)
        {
            ink -= amount;
        }
    }

    void main()
    {
        auto pen = new Pen;
        auto otherPen = pen; // ← Now both variables provide
        writefln("Before: %s %s", pen.ink, otherPen.ink);
        pen.use(1); // ← the same object is used
        otherPen.use(2); // ← the same object is used
        writefln("After : %s %s", pen.ink, otherPen.ink);
    }

Поскольку классы являются ссылочными типами, переменные класса pen и otherPen предоставляют
доступ к одному и тому же объекту Pen. В результате использование любой из этих переменных класса
влияет на один и тот же объект:

.. code-block:: console

    Before: 15 15
    After : 12 12

Этот единственный объект и две переменные класса будут размещены в памяти
как показано на следующем рисунке:

.. image:: images/68.1_1.png

Ссылки указывают на фактические переменные, как это делают pen и otherPen выше.
Языки программирования реализуют концепции ссылок и указателей с помощью
специальных регистров микропроцессора, которые предназначены специально для указания на
места в памяти.
За кулисами D концепции более высокого уровня (переменные класса, срезы, ассоциативные
массивы и т. д.) реализуются с помощью указателей. Поскольку эти высокоуровневые функции
поскольку эти возможности более высокого уровня уже эффективны и удобны, указатели редко нужны в программировании на D.
Тем не менее, программистам на D важно хорошо разбираться в указателях.

Синтаксис
---------

Синтаксис указателей в D в основном такой же, как и в C. Хотя это можно рассматривать как
преимущество, особенности синтаксиса указателей языка C обязательно наследуются и в
D. Например, различные значения символа * могут
сбивать с толку.
За исключением указателей void, каждый указатель связан с определенным
типом и может указывать только на переменные этого конкретного типа. Например, указатель int
может указывать только на переменные типа int.
Синтаксис определения указателя состоит из связанного типа и символа *

.. code-block:: d

    type_to_point_at * name_of_the_pointer_variable;

Соответственно, переменная-указатель, которая будет указывать на переменные int, будет
определена следующим образом:

.. code-block:: d

    int * myPointer;

Символ * в этом синтаксисе может быть произнесен как "указатель". Таким образом, тип
myPointer выше - это "указатель int". Пробелы до и после символа *
являются необязательными. Также распространены следующие синтаксисы:

.. code-block:: d

    int* myPointer;
    int *myPointer;

Когда упоминается именно тип указателя, как в "int pointer", то
принято писать тип без пробелов, как в int*

Значение указателя и оператор адреса &
--------------------------------------

Будучи переменными, указатели также имеют значения. По умолчанию значение
указателя по умолчанию является специальное значение null, которое означает, что указатель не указывает ни на
ни на какую переменную (то есть не предоставляет доступ ни к одной переменной).
Чтобы указатель предоставлял доступ к переменной, его значение должно быть
установить в адрес этой переменной. Указатель начнет указывать на переменную, которая
которая находится по этому адресу. С этого момента я буду называть эту переменную указателем.
Оператор &, который мы уже много раз использовали в readf, также кратко упоминался в
был кратко упомянут в главе "Типы значений и ссылочные типы" (:ref:`Типы значения и ссылочные типы<глава_34_start>`).
Этот оператор выдает адрес переменной, которая записана после него. Его
значение может быть использовано при инициализации указателя:

.. code-block:: d

    int myVariable = 180;
    int * myPointer = &myVariable;

Инициализация myPointer адресом myVariable заставляет myPointer указывать на
myVariable.
Значение указателя совпадает с адресом myVariable:

.. code-block:: d

    writeln("The address of myVariable: ", &myVariable);
    writeln("The value of myPointer   : ", myPointer);

.. code-block:: console

    The address of myVariable: 7FFF2CE73F10
    The value of myPointer   : 7FFF2CE73F10

Примечание: значение адреса может быть разным при каждом запуске программы.
На следующем рисунке показаны эти две переменные в памяти:

.. image:: images/68.3_1.png

Значение myPointer - это адрес myVariable, концептуально указывающий на
переменную, которая находится в этом месте.
Поскольку указатели тоже являются переменными, оператор & может выдать адрес
указателя:

.. code-block:: d

    writeln("The address of myPointer : ", &myPointer);

.. code-block:: console

    The address of myPointer : 7FFF2CE73F18

Поскольку разница между двумя указанными адресами равна 8, помня о том, что int занимает 4 байта.
int занимает 4 байта, мы можем сделать вывод, что myVariable и myPointer находятся на расстоянии 4 байт
друг от друга в памяти.
Убрав стрелку, которая представляла концепцию указания на, мы можем
представить содержимое памяти вокруг этих адресов следующим образом:

.. image:: images/68.3_2.png

Имена переменных, функций, классов и т. д. и ключевые слова не являются частью
программы компилируемых языков, таких как D. Переменные, которые были определены
программистом в исходном коде, преобразуются в байты, которые занимают память или
регистры микропроцессора.
*Примечание: Имена (они же символы) могут быть включены в программы, чтобы помочь в
отладке, но эти имена не влияют на работу программы.*

Оператор доступа \*
--------------------

Выше мы видели, что символ \*, который обычно обозначает
умножение, также используется при определении указателей. Сложность синтаксиса
указателей заключается в том, что этот же символ имеет и третье значение: Он также используется при
при доступе к указателю через указатель.
Когда он пишется перед именем указателя, он означает переменную, на которую указывает указатель
на которую указывает указатель (то есть указатель):

.. code-block:: d

    writeln("The value that it is pointing at: ", *myPointer);

.. code-block:: console

    The value that it is pointing at: 180



Оператор \. (точка) используется для доступа к члену указателя.
---------------------------------------------------------------

Если вы знакомы с указателями из языка C, то этот оператор аналогичен оператору \-\> в этом языке.
языке.
Выше мы видели, что для доступа к указателю используется оператор \*. Это
достаточно полезно для указателей фундаментальных типов, таких как int*: К значению
фундаментального типа можно получить, просто написав \*myPointer.
Однако когда указатель представляет собой структуру или объект класса, тот же синтаксис
становится неудобным. Чтобы понять, почему, рассмотрим следующую структуру:

.. code-block:: d

      struct Coordinate
      {
        int x;
        int y;
        string toString() const
        {
            return format("(%s,%s)", x, y);
        }
    }

Следующий код определяет объект и указатель этого типа:

.. code-block:: d

    auto center = Coordinate(0, 0);
    Coordinate * ptr = &center; // pointer definition
    writeln(*ptr);

Этот синтаксис удобен при обращении к значению всего объекта Coordinate
объекта:

.. code-block:: console

    (0,0)

Однако код становится сложным при обращении к члену объекта
через указатель и оператор \*:

.. code-block:: d

    // Adjust the x coordinate
    (*ptr).x += 10;

Это выражение изменяет значение члена x объекта center. Левая
левая часть этого выражения может быть объяснена следующими шагами:

* ptr: Указатель, указывающий на центр
* \*ptr: Доступ к объекту (т.е. к самому центру)
* (\*ptr): Скобки, чтобы оператор . (точка) применялся к объекту, а не к
  указатель
* (\*ptr).x: Член x объекта, на который указывает ptr.
  Чтобы уменьшить сложность синтаксиса указателей в D, оператор . (точка) переносится
  на указатель и обеспечивает доступ к члену объекта. (Исключения из
  этого правила приведены в конце этого раздела.)
  Таким образом, предыдущее выражение обычно записывается как:

.. code-block:: d

    ptr.x += 10;

Поскольку сам указатель не имеет члена с именем x, .x применяется к
указателю, и член x в center модифицируется:

.. code-block:: console

    (10,0)

Обратите внимание, что это то же самое, что и использование оператора . (точка) с классами. При использовании оператора
. (точка) применяется к переменной класса, он предоставляет доступ к члену объекта класса:

.. code-block:: d

    class ClassType
    {
        int member;
    }
    // ...
    // Variable on the left, object on the right
    ClassType variable = new ClassType;
    // Applied to the variable but accesses the member of
    // the object
    variable.member = 42;

Как вы помните из главы "Классы" (:ref:`Классы<Классы>`), объект класса
создается с помощью ключевого слова new в правой части. variable - это переменная класса.
переменная, обеспечивающая доступ к нему.
Понимание того, что это то же самое, что и с указателями, говорит о том, что переменные класса
и указатели реализуются компилятором одинаково.
Исключение из этого правила есть как для переменных класса, так и для указателей. Тип
свойства, такие как .sizeof, применяются к типу указателя, а не к типу на который указывает указатель:

.. code-block:: d

    char c;
    char * p = &c;
    writeln(p.sizeof); // size of the pointer, not the pointee

.sizeof выдает размер p, который является char*, а не размер c, который является
char. В 64-битной системе указатели имеют длину 8 байт:

.. code-block:: console

    8

Изменение значения указателя
----------------------------

Значения указателей можно увеличивать или уменьшать, и они могут использоваться
при сложении и вычитании:

.. code-block:: d

    ++ptr;
    --ptr;
    ptr += 2;
    ptr -= 2;
    writeln(ptr + 3);
    writeln(ptr - 3);

TODO check this text may be we can make more clear

В отличие от привычных нам арифметических операций, эти операции
не изменяют его значение на указанную величину. Значение индикатора не изменяется на указанную
изменяется, указывая на следующую (или предыдущую) переменную на указанную величину.
Например, увеличение значения указателя с помощью оператора ++
для отображения следующей переменной после предыдущей:

.. code-block:: d

    ++ptr; // Starts pointing at a variable that is next in
           // memory from the old variable

Чтобы это работало правильно, фактическое значение указателя должно быть увеличено на
размер переменной. Например, поскольку размер int равен 4, инкрементирование указателя типа int* изменяет его значение на 4.
указателя типа int* изменяет его значение на 4. Программисту не нужно обращать
внимание на эту деталь; значение указателя изменяется на нужную величину
автоматически.
Предупреждение: Указание на местоположение, которое не является допустимым адресом, является неопределенным поведением
который принадлежит программе. Даже если он не используется для доступа к какой-либо переменной
то указатель не может указывать на несуществующую переменную. (Единственное
исключением из этого правила является указание на воображаемый элемент, расположенный на один конец массива.
конец массива. Это будет объяснено ниже.)
Например, нельзя увеличивать указатель, указывающий на myVariable,
потому что myVariable определена как один int:

.. code-block:: d

    ++myPointer; // ←  неопределённое поведение

Неопределенное поведение означает, что невозможно узнать, каким будет поведение
программы после выполнения этой операции. Могут существовать системы, в которых программа
аварийно завершается после инкрементирования указателя. Однако в большинстве современных систем указатель
указатель, скорее всего, будет указывать на неиспользуемую область памяти, которая, как было показано, находится
между myVariable и myPointer на предыдущем рисунке.
По этой причине значение указателя должно увеличиваться или уменьшаться
только в том случае, если в новом месте находится действительный объект. (Как мы увидим ниже, указание на
на элемент, расположенный на один конец массива, тоже допустимо). Массивы (и срезы) обладают
этим свойством: Элементы массива находятся в памяти рядом друг с другом.
Указатель, указывающий на элемент фрагмента, можно безопасно увеличивать до тех пор.
если он не используется для доступа к элементу, находящемуся за концом фрагмента.
Увеличение такого указателя с помощью оператора ++ приводит к тому, что он указывает на следующий
элемент:

.. code-block:: d

    import std.stdio;
    import std.string;
    import std.conv;

    enum Color
    {
        red,
        yellow,
        blue
    }

    struct Crayon
    {
        Color color;
        double length;
        string toString() const
        {
            return format("%scm %s crayon", length, color);
        }
    }

    void main()
    {
        writefln("Crayon objects are %s bytes each.", Crayon.sizeof);
        Crayon[] crayons = [
            Crayon(Color.red, 11),
            Crayon(Color.yellow, 12),
            Crayon(Color.blue, 13)
        ];
        Crayon* ptr = &crayons[0]; // (1)
        for (int i = 0; i != crayons.length; ++i)
        {
            writeln("Pointer value: ", ptr); // (2)
            writeln("Crayon: ", *ptr); // (3)
            ++ptr; // (4)
        }
    }

#. Определение: Указатель инициализируется адресом первого элемента.
#. Использование его значения: Значение указателя - это адрес элемента, на который он
   на который он указывает.
#. Доступ к элементу, на который указывает указатель.
#. Указание на следующий элемент.

Вывод:

.. code-block:: console

    Crayon objects are 16 bytes each.
    Pointer value: 7F37AC9E6FC0
    Crayon: 11cm red crayon
    Pointer value: 7F37AC9E6FD0
    Crayon: 12cm yellow crayon
    Pointer value: 7F37AC9E6FE0
    Crayon: 13cm blue crayon

Обратите внимание, что цикл выше итерируется в общей сложности crayons.length раз, так что
указатель всегда используется для доступа к правильному элементу.

.. index::
    риски указателей

Указатели - это риск
--------------------

Компилятор и среда выполнения D не могут гарантировать, что указатели
всегда используются правильно. Программист несет ответственность за то, чтобы
указатель либо равен нулю, либо указывает на корректное место в памяти (на переменную, на
элемент массива и т. д.).
По этой причине всегда лучше рассмотреть более высокоуровневые возможности D, прежде чем
прежде чем думать об использовании указателей.

Элемент на один конец массива
-----------------------------

Допустимо указывать на воображаемый элемент, находящийся за одним концом массива.
Это полезная идиома, аналогичная диапазонам чисел. При определении фрагмента
с диапазоном чисел, вторым индексом является один из элементов среза:

.. code-block:: d

    int[] values = [ 0, 1, 2, 3 ];
    writeln(values[1 .. 3]); // 1 and 2 included, 3 excluded

Эту идиому можно использовать и с указателями. Это распространенная конструкция функции в C
и C++, когда один параметр функции указывает на первый элемент, а другой
указывает на элемент после последнего элемента:

.. code-block:: d

    void tenTimes(int* begin, int* end)
    {
        while (begin != end)
        {
            *begin *= 10;
            ++begin;
        }
    }

    void main()
    {
        int[] values = [0, 1, 2, 3];
        // The address of the second element:
        int* begin = &values[1];
        // The address of two elements beyond that one
        tenTimes(begin, begin + 2);
        writeln(values);
    }

Значение begin + 2 означает два элемента после того, на который указывает begin
(т.е. элемент с индексом 3).
Функция tenTimes() принимает два параметра-указателя. Она использует элемент
на который указывает первый, но никогда не обращается к элементу, на который указывает второй
но никогда не обращается к элементу, на который указывает второй. В результате изменяются только элементы с индексами 1 и 2:

.. code-block:: console

    [0, 10, 20, 3]

Такие функции можно реализовать и с помощью цикла for:

.. code-block:: d

    for ( ; begin != end; ++begin) {
        *begin *= 10;
    }

Два указателя, определяющие диапазон, также можно использовать в цикле foreach:

.. code-block:: d

    foreach (ptr; begin .. end) {
        *ptr *= 10;
    }

Чтобы эти методы были применимы ко всем элементам среза, второй
указатель должен обязательно указывать после последнего элемента:

.. code-block:: d

    // The second pointer is pointing at the imaginary element
    // past the end of the array:
    tenTimes(begin, begin + values.length);

Именно поэтому законно указывать на мнимый элемент, расположенный на один дальше
последнего элемента массива.

Использование указателей с оператором индексации массива []
-----------------------------------------------------------

Хотя в D это не является абсолютно необходимым, указатели можно напрямую использовать для
доступа к элементам массива по значению индекса:

.. code-block:: d

    double[] floats = [ 0.0, 1.1, 2.2, 3.3, 4.4 ];
    double * ptr = &floats[2];
    *ptr = -100; // direct access to what it points at
    ptr[1] = -200; // access by indexing
    writeln(floats);

Вывод:

.. code-block:: console

    [0, 1.1, -100, -200, 4.4]


В этом синтаксисе элемент, на который указывает указатель, считается
первым элементом воображаемого фрагмента. Оператор [] предоставляет доступ к
указанному элементу этого фрагмента. Приведенный выше ptr изначально указывает на элемент с
индексом 2 исходного фрагмента с плавающей точкой. ptr[1] - это ссылка на элемент 1
воображаемого фрагмента, который начинается с ptr (т.е. индекс 3 исходного фрагмента).
Хотя такое поведение может показаться сложным,
за этим синтаксисом стоит очень простое преобразование. За кулисами компилятор преобразует
синтаксис указателя[индекса] на выражение \*(указатель + индекс):

.. code-block:: D

    ptr[1] = -200; // slice syntax
    *(ptr + 1) = -200; // the equivalent of the previous line

Как я уже говорил, компилятор не может гарантировать, что это выражение
ссылается на действительный элемент.  Фрагменты D обеспечивают гораздо более безопасную альтернативу, и их следует рассматривать рассматривать вместо использования указателей

.. code-block:: d

    double[] slice = floats[2 .. 4];
    slice[0] = -100;
    slice[1] = -200;

Обратите внимание, что в срезе есть только два элемента. Слайс содержит элементы 2 и 3
обеспечивает доступ к индексированным элементам. Элемент среза с индексом 4
является внешним.
Слайсы безопасны; ошибки доступа к элементам отлавливаются во время выполнения:

.. code-block:: D

    slice[2] = -300; // Runtime error: accessing outside of the slice

Поскольку в приведенном выше фрагменте нет элемента с индексом 2, исключение будет
будет выброшено во время выполнения (если только программа не была скомпилирована с ключом -release
компилятора), значения индексов проверяются для срезов во время выполнения:

.. code-block:: console

    ore.exception.RangeError@deneme(8391): Range violation 68.10 Producing a slice from a pointer

Создание фрагмента из указателя
-------------------------------

Указатели не так безопасны и не так полезны, как срезы, потому что, хотя их можно использовать
с оператором индексации срезов, они не знают о допустимом диапазоне
элементов.
Однако, когда количество допустимых элементов известно, указатель можно использовать
для построения фрагмента.
Предположим, что приведенная ниже функция makeObjects() находится в библиотеке языка C. Предположим
предположим, что makeObjects создает заданное количество объектов Struct и возвращает
указатель на первый из этих объектов:

.. code-block:: D

    Struct * ptr = makeObjects(10);

Синтаксис, позволяющий получить фрагмент из указателя, следующий:

.. code-block:: D

    /* ... */ slice = pointer[0 .. count];

Соответственно, фрагмент из 10 объектов, возвращаемых функцией makeObjects(), может быть
с помощью следующего кода:

.. code-block:: D

    Struct[] slice = ptr[0 .. 10];

После этого определения slice готов к безопасному использованию в программе, как и любой другой
как и любой другой фрагмент:

.. code-block:: D

    writeln(slice[1]); // prints the second element

void* может указывать на любой тип
----------------------------------

Хотя в D он почти никогда не нужен, специальный тип указателя void* языка C доступен
void* может указывать на любой тип:

.. code-block::d

    int number = 42;
    double otherNumber = 1.25;
    void * canPointAtAnything;
    canPointAtAnything = &number;
    canPointAtAnything = &otherNumber;

void* может указывать на переменные двух разных типов: int и
double.
Указатели void* имеют ограниченную функциональность. Как следствие их гибкости,
они не могут предоставлять доступ к указателю. Когда фактический тип неизвестен, его
размер также неизвестен:

.. code-block:: D

    *canPointAtAnything = 43; // ← ошибка компиляции

Вместо этого его значение должно быть сначала преобразовано в указатель правильного типа:

.. code-block:: D

      int number = 42; // (1)
      void * canPointAtAnything = &number; // (2)
      // ...
      int * intPointer = cast(int*)canPointAtAnything; // (3)
      *intPointer = 43; // (4)

1. Фактическая переменная
2. Хранение адреса переменной в void*
3. Присвоение этого адреса указателю нужного типа
4. Модификация переменной через новый указатель

Можно увеличивать или уменьшать значения указателей void*, в этом случае
их значения изменяются так, как если бы они были указателями 1-байтовых типов, таких как ubyte:

.. code-block:: D

    ++canPointAtAnything; // incremented by 1


void* иногда требуется при взаимодействии с библиотеками, написанными на языке C.
Поскольку в языке C нет функций более высокого уровня, таких как интерфейсы, классы, шаблоны и т. д.
библиотеки на Си вынуждены полагаться на тип void*.


Использование указателей в логических выражениях
------------------------------------------------

Указатели могут быть автоматически преобразованы в bool. Указатели, имеющие значение
null, дают значение false, а остальные - true. Другими словами, указатели, которые
не указывают ни на одну переменную, являются ложными.
Рассмотрим функцию, которая печатает объекты на стандартный вывод. Давайте спроектируем
эту функцию так, чтобы она также выдавала количество байт, которые она только что вывела.
Однако пусть она выдает эту информацию только по специальному запросу.
Можно сделать это поведение необязательным, проверяя, является ли значение указателя
указателя равно null или нет


.. code-block:: D

    void print(Crayon crayon, size_t * numberOfBytes) {
        immutable info = format("Crayon: %s", crayon);
        writeln(info);
        if (numberOfBytes) {
            *numberOfBytes = info.length;
        }
    }

Если вызывающей стороне не нужна эта специальная информация, она может передать null в качестве
аргумент:

.. code-block:: D

    print(Crayon(Color.yellow, 7), null);

Если количество байт действительно важно,  то не нулевое значение указателя должно быть передано:

.. code-block:: D

   size_t numberOfBytes;
   print(Crayon(Color.blue, 8), &numberOfBytes);
   writefln("%s bytes written to the output", numberOfBytes);

Обратите внимание, что это всего лишь пример. В противном случае было бы лучше, чтобы такая функция, как
print() возвращать количество байт без каких-либо условий:

.. code-block:: D

    size_t print(Crayon crayon) {
      immutable info = format("Crayon: %s", crayon);
      writeln(info);
      return info.length;
    }

new возвращает указатель для некоторых типов
--------------------------------------------

new, который мы использовали только для конструирования объектов класса, можно использовать
с другими типами: структурами, массивами и фундаментальными типами. Переменные
которые конструируются с помощью new, называются динамическими переменными.
Сначала new выделяет место в памяти для переменной, а затем конструирует
переменную в этом пространстве. Сама переменная не имеет символического имени в
скомпилированной программе; доступ к ней будет осуществляться через ссылку, которую
возвращаемую командой new.
Ссылка, которую возвращает new, имеет разный вид в зависимости от типа
переменной:

* Для объектов класса это переменная класса:

.. code-block:: D

    Class classVariable = new Class;

* Для объектов struct и переменных фундаментальных типов это указатель:

.. code-block:: D

    Struct * structPointer = new Struct;
    int * intPointer = new int;

* Для массивов это фрагмент:

.. code-block:: D

    int[] slice = new int[100];

Это различие обычно не очевидно, когда тип не прописан слева:

.. code-block:: D

    auto classVariable = new Class;
    auto structPointer = new Struct;
    auto intPointer = new int;
    auto slice = new int[100];

Следующая программа выводит возвращаемый тип new для различных типов
переменных:

.. code-block:: D

    import std.stdio;

    struct Struct
    {
    }

    class Class
    {
    }

    void main()
    {
        writeln(typeof(new int).stringof);
        writeln(typeof(new int[5]).stringof);
        writeln(typeof(new Struct).stringof);
        writeln(typeof(new Class).stringof);
    }

new возвращает указатели на структуры и фундаментальные типы:

.. code-block:: console

    int*
    int[]
    Struct*
    Class

Если тип динамической переменной, созданной с помощью new, является типом значения (:ref:`Типы значения и ссылочные типы<глава_34_start>`), то
жизненный цикл переменной, продлевается до тех пор, пока по-крайней мере существуеет хотя бы одна ссылка в программе (например, на
указатель) (это поведение является стандартным для ссылочных типов).

Свойство .ptr массивов
----------------------

Свойство .ptr массивов и срезов - это адрес первого элемента. Тип этого значения - указатель на тип элементов:

.. code-block:: D

    int[] numbers = [ 7, 12 ];
    int * addressOfFirstElement = numbers.ptr;
    writeln("First element: ", *addressOfFirstElement);


Это свойство особенно полезно при взаимодействии с библиотеками языка C. Некоторые функции языка C
функции берут адрес первого из нескольких последовательных элементов в памяти.
Помня, что строки - это тоже массивы, свойство .ptr можно использовать и со строками
строками. Однако обратите внимание, что первый элемент строки не обязательно должен быть
первой буквой строки, а первой кодовой единицей Unicode этой буквы. В качестве
Например, буква é хранится как две кодовые единицы в строке char.
При доступе через свойство .ptr к кодовым единицам строк можно обращаться
по отдельности. Мы увидим это в приведенном ниже разделе примеров

Оператор in ассоциативных массивов
----------------------------------

На самом деле мы уже использовали указатели ранее в главе "Ассоциативные массивы" (:ref:`Ассоциативные массивы<Ассоциативные массивы>`). В той главе я намеренно не назвал точный тип оператора in
и использовал его только в логических выражениях:


.. code-block:: D

    if ("purple" in colorCodes) {
    // there is an element for key "purple"
    } else {
    // no element for key "purple"
    }

На самом деле оператор in возвращает адрес элемента, если существует элемент
для указанного ключа; в противном случае он возвращает null. Приведенный выше оператор if на самом деле
полагается на автоматическое преобразование значения указателя в bool. Когда возвращаемое значение функции in хранится в указателе, доступ к элементу можно получить
через этот указатель

.. code-block:: D

    import std.stdio;

    void main()
    {
        string[int] numbers =
            [0: "zero", 1: "one", 2: "two", 3: "three"];
        int number = 2;
        auto element = number in numbers; // (1)
        if (element)
        { // (2)
            writefln("I know: %s.", *element); // (3)
        }
        else
        {
            writefln("I don't know the spelling of %s.", number);
        }
    }

Элемент переменной-указателя инициализируется значением оператора in
(1) и его значение используется в логическом выражении (2). Доступ к значению элемента осуществляется
через этот указатель (3) только в том случае, если указатель не является нулевым.
Фактический тип элемента выше - это указатель на тот же тип элементов
(то есть значения) ассоциативного массива. Поскольку элементы чисел выше имеют
тип string, in возвращает строку*. Соответственно, тип можно было бы прописать
в явном виде:

.. code-block:: D

    string * element = number in numbers;

Когда использовать указатели
----------------------------

Указатели в D встречаются редко. Как мы уже видели в главе Чтение из стандартного ввода
(:ref:`Чтение из стандартного ввода<Чтение из стандартного ввода>`), readf можно использовать и без явных указателей.

**Когда требуется в библиотеках**

Указатели могут появляться в связках библиотек C и C++. Например, следующая
функция из библиотеки GtkD принимает указатель

.. code-block:: D

    GdkGeometry geometry;
    // ... set the members of 'geometry' ...
    window.setGeometryHints(/* ... */, &geometry, /* ... */);

**При обращении к переменным типа значения**

Для обращения к локальным переменным можно использовать указатели. Следующая программа
подсчитывает результаты подбрасывания монеты. Она использует указатель при
при обращении к одной из двух локальных переменных:

.. code-block:: D

    import std.stdio;
    import std.random;

    void main()
    {
        size_t headsCount = 0;
        size_t tailsCount = 0;
        foreach (i; 0 .. 100)
        {
            size_t* theCounter = (uniform(0, 2) == 1)
                ? &headsCount : &tailsCount;
            ++(*theCounter);
        }
        writefln("heads: %s  tails: %s", headsCount, tailsCount);
    }

Разумеется, есть и другие способы достижения той же цели. Например, использование
тернарного оператора другим способом:

.. code-block:: D

    uniform(0, 2) ? ++headsCount : ++tailsCount;

С помощью оператора if:

.. code-block:: D

    if (uniform(0, 2)) {
    ++headsCount;
    } else {
    ++tailsCount;
    }

**В качестве переменных-членов структур данных**

Указатели необходимы при реализации многих структур данных.
В отличие от элементов массива, которые находятся рядом друг с другом в памяти, элементы
многих других структур данных находятся отдельно друг от друга. Такие структуры данных основаны на
концепции, согласно которой их элементы указывают на другие элементы.
Например, каждый узел связанного списка указывает на следующий узел. Аналогично, каждый
узел двоичного дерева указывает на левую и правую ветви под этим узлом.
Указатели встречаются и в большинстве других структур данных.
Хотя можно воспользоваться преимуществами ссылочных типов D, указатели могут быть
более естественными и эффективными в некоторых случаях.
Ниже мы рассмотрим примеры членов-указателей.

**При прямом доступе к памяти**

Будучи низкоуровневыми функциями микропроцессора, указатели обеспечивают доступ на уровне байтов к
местам памяти. Обратите внимание, что такие места должны принадлежать действительным переменным. Это
попытка получить доступ к случайной ячейке памяти является неопределенным поведением.

Примеры
-------

**Простой связанный список**

Элементы связанных списков хранятся в узлах. Концепция связанного списка
основана на том, что каждый узел указывает на узел, который находится после него. У последнего узла нет
другого узла, на который можно было бы указать, поэтому он устанавливается в null:

.. image:: images/68.17_1.png


Рисунок выше может ввести в заблуждение: В действительности узлы не находятся рядом в
памяти. Каждый узел указывает на следующий узел, но следующий узел может находиться в
совершенно в другом месте.
Для представления узлов такого связного списка можно использовать следующую структуру
списка целых чисел:

.. code-block:: D

    struct Node {
      int element;
      Node * next;
      // ...
    }

*Примечание:Поскольку Node содержит ссылку на тот же тип, что и он сам, он является рекурсивным типом.*
Весь список может быть представлен одним указателем, который указывает на первый
узел, который обычно называют головой:

.. code-block:: D

    struct List {
        Node * head;
        // ...
    }

Чтобы не затягивать пример, определим только одну функцию, которая добавляет элемент в
в начало списка:

.. code-block:: D

    struct List {
      Node * head;
      void insertAtHead(int element) {
        head = new Node(element, head);
      }
      // ...
    }

Строка внутри insertAtHead() сохраняет связь между узлами, добавляя новый узел в
голову списка. (Функция, добавляющая узел в конец списка, была бы более
естественной и более полезной. Мы увидим эту функцию позже в одной из задач.)
Выражение в правой части этой строки создает объект Node. Когда этот
новый объект создается, его следующий член инициализируется текущей главой
списка. Когда головной член списка присваивается этому новому связанному узлу,
новый элемент оказывается первым элементом.
Следующая программа тестирует эти две структуры:

.. code-block:: D

    import std.stdio;
    import std.conv;
    import std.string;

    struct Node
    {
        int element;
        Node* next;
        string toString() const
        {
            string result = to!string(element);
            if (next)
            {
                result ~= " -> " ~ to!string(*next);
            }
            return result;
        }
    }

    struct List
    {
        Node* head;
        void insertAtHead(int element)
        {
            head = new Node(element, head);
        }

        string toString() const
        {
            return format("(%s)", head ? to!string(*head) : "");
        }
    }

    void main()
    {
        List numbers;
        writeln("before: ", numbers);
        foreach (number; 0 .. 10)
        {
            numbers.insertAtHead(number);
        }
        writeln("after : ", numbers);
    }

Вывод

.. code-block:: console

    before: ()
    after : (9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0)


**Наблюдение за содержимым памяти с помощью ubyte**

Данные, хранящиеся по каждому адресу памяти, представляют собой байт. Каждая переменная строится
на участке памяти, состоящем из стольких байтов, сколько составляет размер типа этой
переменной.
Подходящим типом указателя для наблюдения за содержимым участка памяти является ubyte*.
Если адрес переменной присвоен указателю ubyte, то все байты
этой переменной можно наблюдать, увеличивая указатель.
Рассмотрим следующее целое число, которое инициализируется шестнадцатеричной
чтобы было понятно, как его байты размещаются в памяти:

.. code-block:: D

    int variable = 0x01_02_03_04

Указатель, указывающий на эту переменную, может быть определен следующим образом:

.. code-block:: D

    int * address = &variable;

Значение этого указателя может быть присвоено указателю ubyte с помощью оператора cast:

.. code-block:: D

    ubyte * bytePointer = cast(ubyte*)address;

Такой указатель позволяет обращаться к четырем байтам переменной int по отдельности:

.. code-block:: D

    writeln(bytePointer[0]);
    writeln(bytePointer[1]);
    writeln(bytePointer[2]);
    writeln(bytePointer[3]);

Если ваш микропроцессор устроен по принципу little-endian, как мой, то вы должны видеть байты
значение 0x01_02_03_04 в обратном порядке:

.. code-block:: console

    4
    3
    2
    1

Используем эту идею в функции, которая будет полезна при наблюдении за байтами всех
типов переменных:

.. code-block:: D

    import std.stdio;

    void printBytes(T)(ref T variable)
    {
        const ubyte* begin = cast(ubyte*)&variable; // (1)
        writefln("type : %s", T.stringof);
        writefln("значение : %s", variable);
        writefln("адрес: %s", begin); // (2)
        writef("байты : ");
        writefln("%(%02x %)", begin[0...T.sizeof]); // (3)
        writeln();
    }

#. Присвоение адреса переменной указателю ubyte.
#. Печать значения указателя.
#. Получение размера типа по .sizeof и печать байтов переменной
   переменной. (Обратите внимание, как из указателя begin создается фрагмент, который затем
   напечатан непосредственно с помощью writefln().

Другим способом печати байтов было бы применение оператора * по отдельности:

.. code-block:: D

    foreach (bytePointer; begin ... begin + T.sizeof)
    {
        writef("%02x ", *bytePointer);
    }

Значение bytePointer изменится с begin на begin + T.sizeof, чтобы
чтобы посетить все байты переменной. Обратите внимание, что значение begin + T.sizeof находится
находится за пределами диапазона и никогда не будет посещено.
Следующая программа вызывает printBytes() с различными типами переменных:

.. code-block:: D

      struct Struct
      {
          int first;
          int second;
      }

      class Class
      {
          int i;
          int j;
          int k;
          this(int i, int j, int k)
          {
              this.i = i;
              this.j = j;
              this.k = k;
          }
      }

      void main()
      {
          int integerVariable = 0x11223344;
          printBytes(integerVariable);
          double doubleVariable = double.nan;
          printBytes(doubleVariable);
          string slice = "a bright and charming façade";
          printBytes(slice);
          int[3] array = [1, 2, 3];
          printBytes(array);
          auto structObject = Struct(0xaa, 0xbb);
          printBytes(structObject);
          auto classVariable = new Class(1, 2, 3);
          printBytes(classVariable);
      }

Выходные данные программы информативны:

.. code-block:: console

      type   : int
      value  : 287454020
      address: 7FFF19A83FB0
      bytes  : 44 33 22 11 ← (1)
      type   : double
      value  : nan
      address: 7FFF19A83FB8
      Pointers
      442
      bytes  : 00 00 00 00 00 00 f8 7f ← (2)
      type   : string
      value  : a bright and charming façade
      address: 7FFF19A83FC0
      bytes  : 1d 00 00 00 00 00 00 00 e0 68 48 00 00 00 00 00
      ← (3)
      type   : int[3LU]
      value  : [1, 2, 3]
      address: 7FFF19A83FD0
      bytes  : 01 00 00 00 02 00 00 00 03 00 00 00 ← (1)
      type   : Struct
      value  : Struct(170, 187)
      address: 7FFF19A83FE8
      bytes  : aa 00 00 00 bb 00 00 00 ← (1)
      type   : Class
      value  : deneme.Class
      address: 7FFF19A83FF0
      bytes  : 80 df 79 d5 97 7f 00 00 ← (4)

**Наблюдения:**

1. Несмотря на обратный порядок в little-endian системах, байты некоторых
типов выглядят так, как и следовало ожидать: Байты располагаются в памяти рядом друг с другом
для ints, массивов фиксированной длины (int[3]) и объектов struct.
2. Учитывая, что байты специального значения double.nan также расположены в памяти в обратном порядке.
обратном порядке в памяти, мы видим, что оно представлено специальным битовым шаблоном
шаблоном 0x7ff80000000000.
3. Сообщается, что строка состоит из 16 байт, но в нее невозможно вместить
буквы "яркий и очаровательный фасад" в такое количество байт невозможно. Это связано с тем.
тем, что за кулисами string на самом деле реализована как struct.
Префикс ее имени __ подчеркивает тот факт, что это внутренний тип, используемый
компилятором, эта структура похожа на следующую:
struct __string {
size_t length;
char * ptr; // собственно символы
}
Доказательство этого факта скрыто в байтах, выведенных для строки
выше. Обратите внимание, что, поскольку ç состоит из двух кодовых единиц UTF-8, 28 букв строки
строка "яркий и очаровательный фасад" состоит в общей сложности из 29 байт.
Значение 0x00000000000000001d, первые 8 байт строки в
выше, также равно 29. Это сильный признак того, что строки действительно размещаются
в памяти, как в приведенной выше структуре.
4. Аналогично, невозможно уместить три члена int объекта класса в 8
байт. Приведенный выше вывод намекает на возможность того, что за кулисами переменная класса
переменная реализована как единственный указатель, который указывает на реальный объект класса
объект:
struct __Class_VariableType {
__Class_ActualObjecType * object;
}
Теперь рассмотрим более гибкую функцию. Вместо того чтобы печатать байты переменной
переменной, давайте определим функцию, которая печатает заданное количество байт в указанном
месте:

.. code-block:: D

      import std.stdio;
      import std.ascii;

      void printMemory(T)(T* location, size_t length)
      {
          const ubyte* begin = cast(ubyte*) location;
          foreach (address; begin .. begin + length)
          {
              char c = (isPrintable(*address) ? *address : '.');
              writefln("%s:  %02x  %s", address, *address, c);
          }
      }

Поскольку некоторые единицы кода UTF-8 могут соответствовать управляющим символам терминала
терминала и нарушить его вывод, мы печатаем только печатаемые символы, предварительно
проверяя их по отдельности с помощью std.ascii.isPrintable(). Непечатаемые
печатаются в виде точки.
Мы можем использовать эту функцию для печати кодовых единиц строки в формате UTF-8 через ее свойство
.ptr:

.. code-block:: D

      import std.stdio;

      void main()
      {
          string s = "a bright and charming façade";
          printMemory(s.ptr, s.length);
      }

Как видно из вывода, буква ç состоит из двух байтов:

.. code-block:: D

   47B4F0:  61  a
   47B4F1:  20
   47B4F2:  62  b
   47B4F3:  72  r
   47B4F4:  69  i
   47B4F5:  67  g
   47B4F6:  68  h
   47B4F7:  74  t
   47B4F8:  20
   47B4F9:  61  a
   47B4FA:  6e  n
   47B4FB:  64  d
   47B4FC:  20
   47B4FD:  63  c
   47B4FE:  68  h
   47B4FF:  61  a
   47B500:  72  r
   47B501:  6d  m
   47B502:  69  i
   47B503:  6e  n
   47B504:  67  g
   47B505:  20
   47B506:  66  f
   47B507:  61  a
   47B508:  c3  .
   47B509:  a7  .
   47B50A:  61  a
   47B50B:  64  d
   47B50C:  65  e

Упражнения
----------

1. Исправьте следующую функцию так, чтобы значения передаваемых ей аргументов
поменялись местами. Для этого упражнения не указывайте параметры как ref, а
воспринимайте их как указатели:

.. code-block:: D

      void swap(int lhs, int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }

      void main()
      {
          int i = 1;
          int j = 2;
          swap(i, j);
          // Their values should be swapped
          assert(i == 2);
          assert(j == 1);
      }

Когда вы запустите программу, вы заметите, что проверки утверждений в настоящее время
не работают.
2. Преобразуйте связный список, который мы определили выше, в шаблон, чтобы его можно было
чтобы его можно было использовать для хранения элементов любого типа.
3. Естественнее всего добавлять элементы в конец связного списка. Измените список таким образом.
чтобы можно было добавлять элементы и в конец.
Для этого упражнения пригодится дополнительная переменная-член-указатель, указывающая на
последний элемент.
